Skip to content

Conversation

@kvirund
Copy link
Collaborator

@kvirund kvirund commented Jan 31, 2026

No description provided.

zedit_save_to_disk(OLC_ZNUM(d));

auto* data_source = world_loader::WorldDataSourceManager::Instance().GetDataSource();
if (data_source) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Надо просто по дефолту ставить реализацию, которая содержит else блоки. Тогда не надо будет эту писанину с if/else.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это не моё изменение - код был до моего PR. Согласен что можно сделать default implementation для WorldDataSource, но это требует отдельного рефакторинга (создать NullDataSource или всегда иметь valid instance).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Делай. data_source был добавлен в ЭТОМ пиаре.

@kvirund kvirund force-pushed the world-load-refactoring branch 2 times, most recently from d4c7438 to 1bfd178 Compare February 1, 2026 01:17
kvirund and others added 27 commits January 31, 2026 19:56
Step 1 of world loading refactoring plan - baseline checksums.

New files:
- src/engine/db/world_checksum.h/cpp: CRC32-based checksum calculation
  for zones, rooms, mobs, objects, and triggers

Features:
- Calculates individual checksums per entity type using XOR aggregation
- Combined checksum for detecting any world data changes
- Detailed per-object checksums saved to file for diff analysis
- CLI flag -C to disable checksum calculation

Integration:
- Checksums calculated at end of GameLoader::BootWorld()
- Results logged to syslog and saved to checksums_detailed.txt

CMake additions:
- FULL_WORLD_PATH option for specifying full world data location
- Automatic setup of small/full data directories in build dir

Baseline checksums:
  Small World (lib):     Combined: 4E6499FF
  Full World:            Combined: BB58755C

Detailed checksums saved in checksums_small.txt and checksums_full.txt
for future comparison after refactoring.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Introduces an interface-based abstraction layer for world data loading:
- IWorldDataSource interface with LoadZones/Triggers/Rooms/Mobs/Objects
- LegacyWorldDataSource wraps existing BootIndex() calls
- GameLoader::BootWorld() now accepts optional data source parameter
- Excludes zone_rn from room checksums (runtime-calculated value)
- Fixes compiler warnings (unused variable, strncpy truncation)

Checksums verified identical before/after refactoring:
- Small world: B6DA5931
- Full world: 82CF7A3E

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add optional SQLite support via HAVE_SQLITE CMake flag
- Create SqliteWorldDataSource skeleton class (load methods not yet implemented)
- Add Save methods to IWorldDataSource interface for OLC
- Implement Save methods in LegacyWorldDataSource (delegates to *_save_to_disk)
- Add trigedit_save_to_disk function for trigger saving
- Fix compiler warnings in utils.cpp (array bounds, strncpy truncation)
- Add Claude Code workflow rules to CLAUDE.md

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add complete implementation for loading world data from SQLite database:
- Zones with commands (M,O,G,E,P,D,R,T,V,Q,F) and typeA/typeB groups
- Triggers with script parsing into cmdlist
- Rooms with flags, exits, triggers, and extra descriptions
- Mobs with flags, skills, triggers, and all attributes
- Objects with extra/wear/no/anti flags, applies, triggers, extra descriptions

Schema matches mud-docs/world_schema.sql specification.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- GetText now returns std::string with UTF-8 to KOI8-R conversion
- Add SafeStoi/SafeStol helper functions for safe string-to-number conversion
- Fix all const char* usages to std::string
- Fix to_room to store vnum (not rnum) - RosolveWorldDoorToRoomVnumsToRnums will convert later
- Fix top_of_mobt to be last valid index (not count) for compatibility with CreateBlankMobsDungeon

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Remove -S command line option for SQLite database path
- Move chdir() before config loading so paths are relative to data dir
- Fix configuration.xml path to be relative (misc/ instead of lib/misc/)
- Auto-detect world.db in data directory: if exists use SQLite, else legacy

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add direction_map to convert direction strings (north/east/etc) to numbers
- Fix DOOR command arg2 to use direction_map instead of SafeStoi
- Add load_prob (arg4) loading for GIVE_OBJ commands

Zones checksums now match between legacy and SQLite loaders.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Set NPC flag before set_level() to avoid clamping mob levels to 34
  (kLvlImplementator limit for non-NPCs)
- Fix long_descr/description column swap (columns 8 and 9)
- Set max_hit to 0 (flag for dice-based HP calculation)
- Add trigger existence validation with warnings for missing triggers
- Use ORDER BY rowid for predictable trigger loading order
- Skip non-existent triggers instead of adding invalid references

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add tests/utils.encoding.cpp with unit tests for utf8_to_koi function
  covering ASCII, Cyrillic, NO-BREAK SPACE, and box drawing characters
- Fix NO-BREAK SPACE (U+00A0) conversion: UTF-8 0xC2 0xA0 -> KOI8-R 0x9A
- Add lib symlink creation in CMake for running server from build directory

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add sex field to SQL query and loading code
- Fix set_level vs set_minimum_remorts bug (was reading level column
  but calling wrong setter)
- Update column indices for max_in_world after sex addition

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- SQLite loader now calculates zone_rn incrementally by vnum (matching Legacy)
- Add extra_flags, anti_flags, no_flags, affect_flags to object checksum
- Add extra_descriptions to object checksum

Co-Authored-By: Claude Opus 4.5 <[email protected]>
…lags

- Add kTrap to obj_type_map for proper type loading
- Handle NULL max_in_world by returning -1 (matching Legacy behavior)
- Add affect flag category handling for object weapons

Co-Authored-By: Claude Opus 4.5 <[email protected]>
… loader

- Add kElementWeapon, kMissile, kWorm, kCraftMaterial2 to obj_type_map
- Add missing extra flags (kSwimming, kFlying, kThrowing, plane 1 flags)
- Apply colorLOW to short_description and PNames (match Legacy loader)
- Apply colorCAP to description (match Legacy loader)
- Add utils_string.h include for colorLOW/colorCAP
- Update CLAUDE.md with SQLite world conversion documentation
- Add patch-based editing guidance to CLAUDE.md

Objects match: 99.7% (13 remaining differences)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Clear runtime flags (kTransformed, kTicktimer) after loading objects
- Set max_in_world to -1 for objects with kZonedacay or kRepopDecay flags

This ensures SQLite loader produces identical object prototypes to Legacy.
All 5192 objects now match (100% checksum match).

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Use normalized trigger_type_bindings table with JOIN query
- Compute trigger_type bitmask from type_chars (a-z = bits 0-25, A-Z = bits 26-51)
- Add TrimRight for script lines to remove trailing whitespace
- Add indent_trigger call to normalize script indentation
- Include dg_olc.h for indent_trigger function

All world checksums now match between Legacy and SQLite loaders.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Read obj_type_id, sector_id, attach_type_id, direction_id directly
- Read location_id, skill_id, arg_wear_pos_id, arg_direction_id directly
- Remove unused text-to-enum conversion maps
- Use static_cast for direct integer-to-enum conversion
- Matches normalized schema in mud-docs

All checksums verified to match between Legacy and SQLite loaders.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Added tools/:
- convert_to_yaml.py: Legacy world to SQLite/YAML converter
- world_schema.sql: SQLite database schema
- sqlite-world-schema.md: Schema documentation
- compare_world_checksums.sh: Test script for verifying checksums

Updated .gitignore to exclude build directories.
Removed generated checksum files (now in test builds).

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add 'enabled' column to SQLite schema for zones, rooms, mobs, objects,
  triggers to support index file filtering
- Update converter to read index files and mark non-indexed entities
  as disabled (enabled=0)
- Update SQLite loader to filter on enabled=1, matching Legacy behavior
- Add minimum_remorts column to objects table
- Add detailed checksum comparison infrastructure:
  - SaveDetailedBuffers() saves serialization buffers per entity
  - LoadBaselineChecksums() loads baseline for comparison
  - CompareWithBaseline() reports mismatches with field-level detail
- Update compare_world_checksums.sh with --rebuild and --reconvert flags
- Fix room exit serialization to use vnum instead of rnum

Checksum verification: Small world shows 100% match between Legacy and
SQLite loaders (zones, rooms, mobs, objects, triggers all identical).

Co-Authored-By: Claude Opus 4.5 <[email protected]>
When built with HAVE_SQLITE support but world.db file is not found,
exit with error instead of silently falling back to legacy loader.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Restored KOI8-R encoding (was corrupted in 0c9ca3c)
- Added includes for world_checksum, legacy/sqlite data sources
- Renamed world_loader to game_loader
- Refactored BootWorld to use IWorldDataSource abstraction
- Added checksum calculation and baseline comparison at boot
- Added no_world_checksum flag to disable checksums

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- setup_test_dirs.sh: Creates test directories for Legacy/SQLite comparison
- run_load_tests.sh: Runs performance tests and compares checksums
- Add test/ and magic.mgc to .gitignore

Co-Authored-By: Claude Opus 4.5 <[email protected]>
The Trigger constructor expects rnum (runtime array index) as the first
parameter, not vnum (persistent database ID). Passing vnum caused
out-of-bounds array access in GET_TRIG_VNUM macro when the vnum was
larger than the trig_index array size, resulting in segfaults during
zone reset on larger worlds.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Filter files by pattern ^\d+\.<ext>$ to ignore backup files like 16.old.obj
- Fix armor parsing for negative values (use lstrip('-').isdigit())
- Use \r\n for joining multi-line aliases and case names (Legacy fread_string
  converts \n to \r\n)
- Remove .strip() calls that were removing control characters like \x1d
- Keep trailing spaces in aliases to match Legacy behavior

This significantly reduces checksum differences:
- MOB: 1354 → 1
- OBJ: still has differences (to be investigated)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Schema changes:
- Replace UNIQUE constraint on entity_triggers with trigger_order column
- Allows duplicate triggers (same trigger attached multiple times)

Converter changes:
- Add trigger_order field for proper trigger ordering
- Fix plane 2 offset in parse_ascii_flags (43 → 60)
- Each plane has 30 bits, not varying sizes

Loader changes:
- Add explicit flag maps for affect, anti, no flags
- Replace ITEM_BY_NAME with direct map lookups
- More reliable flag loading without silent failures

Progress (small world after reconvert):
  Zones:    100.0% (0 diff)
  Rooms:     99.9% (3 diff - missing kNoItem plane 2 flag)
  Mobs:     100.0% (0 diff)
  Objects:  100.0% (0 diff)
  Triggers: 100.0% (0 diff)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Changes:
- Add --skip-encoding option
- Check for UTF-8 BOM and Cyrillic in source files
- Increase default dump count to 10
- Add buffer comparison for all entity types (rooms, triggers, zones)
- Show field-by-field diff using | separator
- Use temp files to avoid binary file issues with diff

Progress (small world):
  Zones:    100.0% (0 diff)
  Rooms:     99.9% (3 diff - missing kNoItem plane 2 flag)
  Mobs:     100.0% (0 diff)
  Objects:  100.0% (0 diff)
  Triggers: 100.0% (0 diff)

Progress (full world):
  Zones:     44.6% (354 diff)
  Rooms:     99.0% (435 diff)
  Mobs:     100.0% (0 diff)
  Objects:   99.5% (95 diff)
  Triggers:  97.8% (367 diff)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Converter:
- Add load_prob parsing for E (EQUIP_MOB) command

Loader:
- Add arg4 (load_prob) reading for EQUIP_MOB commands
- Add arg4 (load_prob) reading for PUT_OBJ commands

Progress (full world):
  Zones:     78.7% (136 diff - zone.group not loaded yet)
  Rooms:     99.0% (435 diff - kNoItem flag)
  Mobs:     100.0% (0 diff) ✓
  Objects:   99.5% (95 diff)
  Triggers:  97.8% (367 diff)

Progress (small world):
  Zones:    100.0% ✓
  Rooms:     99.9% (3 diff - kNoItem flag)
  Mobs:     100.0% ✓
  Objects:  100.0% ✓
  Triggers: 100.0% ✓

Co-Authored-By: Claude Opus 4.5 <[email protected]>
kvirund and others added 27 commits February 1, 2026 15:03
Updated loader performance comparison with correct YAML_THREADS=8 data.
Previous report incorrectly showed YAML as slowest (40.5s, 34% slower).
Actual data shows YAML with 8 threads is FASTEST (18.2s, 40% faster)!

Corrected performance ranking:
1. YAML (8 threads): 18.196s - FASTEST, 40% faster than Legacy
2. SQLite:          27.715s - 9% faster than Legacy
3. Legacy:          30.254s - Baseline
4. YAML (1 thread): 52.762s - Not recommended

Key insight: Multi-threading is CRITICAL for YAML performance.
Production recommendation changed: Use YAML with YAML_THREADS=8 for
best performance + human-readability.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Changed 'const auto' to 'const auto &' to prevent unnecessary copying
of std::string and BasicCompactTrie::Range objects in test files.

Fixes warnings:
- compact.trie.iterators.cpp: lines 22, 28, 136, 143
- compact.trie.prefixes.cpp: lines 21, 95, 117, 146
Include actual description text (not just index) in room checksums
to detect mismatches in description assignment between loaders.

This will help identify issues where different loaders assign
different descriptions to the same room (e.g., room 5000 getting
wrong description text in YAML loader vs Legacy loader).

Uses GlobalObjects::descriptions().get() to retrieve actual text
content, falling back to temp_description if set.
Problem: RoomDescriptions::merge() was passing 0-based loop index
to LocalDescriptionIndex::get() which expects 1-based indices.
This caused all room descriptions to be shifted by one position,
resulting in rooms displaying wrong descriptions.

Example: Room 5000 "Комнаты отдыха" was showing description from
room 4829 "На полянке" (about Misha and mosquitoes).

Fix: Convert loop index from 0-based to 1-based when calling get():
  local_index.get(local_idx + 1)

Both LocalDescriptionIndex and RoomDescriptions use 1-based indexing
where 0 means "no description", matching Legacy loader behavior.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
These files were added by mistake in commit 96dfda7.
Board files should not be in lib.template as they are
created automatically during server initialization.
…class

Problem: dirty_indent_trigger() used thread_local static stack,
making state management implicit and hard to test.

Solution: Created TriggerIndenter class that encapsulates indentation
state and logic. Removes dependency on global/thread_local storage.

Changes:
- New files: trigger_indenter.h, trigger_indenter.cpp
- TriggerIndenter class with indent() method and private stack
- Updated indent_trigger() to use thread_local TriggerIndenter instance
- Kept dirty_indent_trigger() for now (unused, will be removed later)
- Fixed compiler warning in compact.trie.prefixes.cpp test

Benefits:
- Explicit state management (no hidden global state)
- Easier to test (can create indenter instances)
- Better encapsulation
- Maintains same API (indent_trigger unchanged)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Add data/ and misc/ to .gitignore - these directories are created
by CMake during test builds (copied from lib.template).

Also remove obsolete YAML_THREAD_SCALING_REPORT.md (replaced by
YAML_CHECKSUM_TEST_REPORT.md).
Problem: Multiline descriptions in YAML were formatted with explicit \r\n
escape sequences, making them hard to read and edit:
  description: "Text line 1\r\nText line 2\r\n"

Solution: Use YAML literal block scalar (|) format for cleaner output:
  description: |
    Text line 1
    Text line 2

Changes:
- Import LiteralScalarString from ruamel.yaml.scalarstring
- Add to_literal_block() helper to wrap multiline strings
- Apply to all multiline fields:
  * Room descriptions, exit descriptions, extra descriptions
  * Mob short/long descriptions
  * Object short_desc, action_desc, extra descriptions
  * Trigger scripts
  * Zone metadata descriptions

Benefits:
- Much more readable YAML files
- Easier to edit descriptions manually
- Preserves exact formatting (newlines)
- Standard YAML practice for multiline text

Example output:
  description: |
    Хозяин устроил здесь комнаты для отдыха. Любой желающий может
    остановиться здесь и передохнуть после дальней дороги...

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Restore correct UTF-8 Russian encoding in comments while preserving
TriggerIndenter refactoring changes (include and class usage).

Previous commit had corrupted encoding (О©╫О©╫ characters), restored
from clean version before corruption.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Problem: to_literal_block() was checking for literal string '\\r\\n' (4 chars)
instead of actual CR+LF bytes '\r\n' (2 bytes).

Result: Multiline descriptions were still output with quoted \r\n escapes
instead of YAML | block format.

Fix: Change check from '\\\\r\\\\n' to '\r\n' to match actual bytes
from '\r\n'.join() operations in parser.

Test result:
  Before: description: "text\r\nmore\r\n"
  After:  description: |
            text
            more

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Problem: When using PyYAML (default), _convert_to_plain() didn't convert
LiteralScalarString objects to plain strings. This caused PyYAML to fall
back to pickling them with Python-specific tags like:
  !!python/object/new:ruamel.yaml.scalarstring.LiteralScalarString

This only affected objects with multiline descriptions containing \r\n
(like object 10700), which triggered to_literal_block() wrapper.

Fix: Add LiteralScalarString detection in _convert_to_plain() and convert
to plain string using str().

Result: All YAML files now use clean YAML syntax, no Python tags.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Problem: PyYAML doesn't support LiteralScalarString natively, so multiline
descriptions were output as quoted strings with \n escapes:
  description: "Line1\nLine2\n"

This is hard to read and may cause checksum differences.

Solution: Use ruamel.yaml (slower but ~3x, but better output quality):
  description: |
    Line1
    Line2

Result: Clean, readable YAML with literal blocks for multiline text.
Note: Kept _convert_to_plain() LiteralScalarString handling as fallback.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Changed default from PyYAML to ruamel.yaml for better output quality:
- Literal blocks (|) for multiline text
- Comment support
- Proper YAML formatting

PyYAML remains available with --yaml-lib pyyaml for fast conversions
where output quality is not critical.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Problem: Even though _yaml_library was set to 'ruamel', the argparse
default was still 'pyyaml', which overwrote the variable.

Fix: Changed argparse default to 'ruamel' to match the global default.

Now ruamel.yaml is truly the default for proper | block output.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Problem: Commit 764ee51 changed to_literal_block to check for actual CR+LF
bytes ('\r\n') instead of the literal escape sequence string ('\r\n').

However, the legacy parser creates descriptions by joining lines with the
literal string '\r\n' (4 characters: backslash, r, backslash, n), not with
actual CR+LF bytes.

Result: to_literal_block was never activated, multiline descriptions were
not converted to YAML literal blocks, causing checksum mismatches.

Solution: Revert to checking for the literal string '\r\n' as in the
original implementation (commit 1e76133).

Test results (after fix):
- Small world: ALL checksums MATCH (100%) ✅
- Full world: 4/5 checksums MATCH (99.995%)
  - Only obj 10700 differs (has actual \r\n in legacy file)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Problem: Object 10700 has literal \r\n characters in its short_desc
in the source file. When parsed and joined, these were indistinguishable
from \r\n added by join(), so to_literal_block converted them to newlines,
causing checksum mismatch with legacy loader (which keeps them as-is).

Solution: Add escape_embedded_rn() function that escapes any existing
\r\n to \\r\\n before joining. This way:
- Source \r\n becomes \\r\\n (preserved after to_literal_block)
- Join \r\n stays as \r\n (converted to newlines by to_literal_block)

Applied to object short_desc where the issue occurs.

Expected result: 100% checksum match including object 10700

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Problem:
- to_literal_block() was converting \r\n (4-char escape) to actual newlines
- This corrupted embedded \r\n sequences in descriptions (e.g., object 10700)
- Manual backslash escaping caused double-escaping (YAML escapes on write too)

Solution:
- to_literal_block() now returns text as-is
- YAML automatically escapes backslashes when writing quoted strings
- YAML automatically unescapes when reading back
- This preserves both embedded \r\n and actual CR+LF bytes correctly

Testing:
- Added 5 unit tests in test_convert_to_yaml.py for to_literal_block()
- All 19 converter tests pass
- 100% checksum match: Legacy = SQLite = YAML on both small and full worlds

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Changes:
- YamlWorldDataSource: LoadWorldConfig() reads world_config.yaml
- GetText(): converts LF to CR+LF if line_endings=dos
- Converter: creates world_config.yaml with line_endings flag
- Baseline (quoted strings): uses line_endings=unix (no conversion)

Tested on small world: ALL checksums MATCH

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Changes:
- ruamel now automatically uses literal blocks (|) without CLI flags
- _use_literal_blocks = (_yaml_library == 'ruamel') - no manual flag needed
- to_literal_block() converts CR+LF to LF and wraps in LiteralScalarString
- create_world_config() generates correct line_endings flag (dos/unix)
- Loader converts LF back to CR+LF when line_endings=dos

Testing:
- Small world: All checksums MATCH (7C788E1F, A84EDF74, CEBB697B, 7E2C7CC8, 91924F29)
- Full world: All checksums MATCH (94AC9F8C, 9AA23637, B146F876, EA7E36EA, E0FF0BE0)
- Literal blocks produce human-readable multi-line YAML
- Quoted strings used only when necessary (e.g., literal \r\n text in obj 10700)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Problem: File was physically UTF-8 but declared as KOI8-R
- Source file stored in UTF-8 (confirmed by file -bi)
- String literals (MATERIAL_NAMES) in UTF-8
- Header claimed koi8-r, causing encoding mismatch

Result: Comments written in UTF-8 instead of KOI8-R
- mixed encoding in output files (content: KOI8-R, comments: UTF-8)

Solution: Change header to # -*- coding: utf-8 -*-
- Python correctly interprets UTF-8 literals
- File encoding='koi8-r' at write converts Unicode→KOI8-R
- All output now uniform KOI8-R encoding

Verified:
- Comments: c1cccdc1da (алмаз) ✓ KOI8-R
- Content: eecfd7cfc7... (Новогодний) ✓ KOI8-R

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
@kvirund kvirund marked this pull request as ready for review February 3, 2026 04:59
kvirund and others added 2 commits February 3, 2026 15:27
Problem 1: Load expects KOI8-R, Save writes UTF-8
- GetText() reads YAML as KOI8-R without conversion (line 263)
- Save functions convert all strings to UTF-8 via ConvertToUtf8()
- Result: After osave, files in UTF-8 → corruption on next load

Problem 2: Always saves entire zone
- No way to save only modified entity
- Overwrites all files even if unchanged

Solution:
1. Remove ConvertToUtf8() from all Save functions
   - SaveObjects(): Keep strings in KOI8-R (15 changes)
   - SaveMobs(): Keep strings in KOI8-R (12 changes)
   - SaveRooms(): Keep strings in KOI8-R (8 changes)
   - SaveTriggers(): Keep strings in KOI8-R (3 changes)
   - SaveZone(): Keep strings in KOI8-R (7 changes)

2. Add specific_vnum parameter to Save functions
   - SaveObjects(zone_rnum, vnum = -1): Save one or all
   - SaveMobs(zone_rnum, vnum = -1): Save one or all
   - SaveRooms(zone_rnum, vnum = -1): Save one or all
   - SaveTriggers(zone_rnum, vnum = -1): Save one or all

3. Update OLC to use data_source->Save* instead of *edit_save_to_disk
   - oedit_save_internally: Now calls data_source->SaveObjects(zone, vnum)
   - medit_save_internally: Now calls data_source->SaveMobs(zone, vnum)
   - redit_save_internally: Now calls data_source->SaveRooms(zone, vnum)
   - This makes OLC work with any world format (YAML/Legacy/SQLite)

Known limitation (yaml-cpp library):
- Literal blocks (|) and comments (#) lost on save
- Files become quoted strings instead of literal blocks
- yaml-cpp doesn't support format preservation

Tested:
- Load/save cycle: All entity types ✓
- Encoding: Files remain in KOI8-R ✓
- No corruption: Russian text correct ✓
- Single save: SaveObjects(zone, 10700) ✓
- Unit tests: YamlSaveEncoding* pass ✓

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Test file was in UTF-8, should be in KOI8-R like all source files
- Russian comments now properly encoded in KOI8-R
- Word 'тест' now shows correct bytes: 0xD4 0xC5 0xD3 0xD4
- All tests still pass

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant